#include "lv_draw_gif_player.h"

#ifdef USE_LV_DRAW_GIF_PLAYER

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <stdbool.h>
#include <assert.h>

#include "lvgl/src/lv_misc/lv_mem.h"
#include "lvgl/src/lv_misc/lv_fs.h"
#include "lvgl/src/lv_widgets/lv_img.h"
#include "lv_draw_gif_player.h"
#include "lv_img_cache.h"

#define LOGE(fmt, ...)   printf(fmt, ##__VA_ARGS__);
#define LOGW(fmt, ...)   printf(fmt, ##__VA_ARGS__);
#define LOGI(fmt, ...)   printf(fmt, ##__VA_ARGS__);
#define LOGD(fmt, ...)//   printf(fmt, ##__VA_ARGS__);

#define DEBUG_MEM 0

//Spec relative, can't be configured
#define GIF_HEADER_SIZE             6
#define GIF_LSD_SIZE                7
#define GIF_NOT_FREE_FLAG           0xbe

#define LZW_DICT_COUNT_MAX  4096

typedef struct {
    void * raw_data;
    uint32_t offset;
    uint32_t size;
    lv_img_src_t src_type;
} gif_raw_info_t;

#if DEBUG_MEM
static int malloc_size = 0; // for debug
#endif
static void * gif_malloc(uint32_t size)
{
    void * p;

    p = lv_mem_alloc(size);

#if DEBUG_MEM
    int size_1 = size;
#ifdef LV_MEM_ENV64
    /*Round the size up to 8*/
    if(size_1 & 0x7) {
        size_1 = size_1 & (~0x7);
        size_1 += 8;
    }
#else
    /*Round the size up to 4*/
    if(size_1 & 0x3) {
        size_1 = size_1 & (~0x3);
        size_1 += 4;
    }
#endif
    if(p) {
        malloc_size += size_1;
        printf("gif_malloc: total: %d\n", malloc_size);
    }
#endif
    memset(p, 0, size);

    return p;
}

static void gif_free(void * p)
{
#if DEBUG_MEM
    malloc_size -= _lv_mem_get_size(p);
    printf("gif_free: total: %d\n", malloc_size);
#endif

    lv_mem_free(p);
    return;
}

static int lzw_reset(struct lzw_info_t * lzw)
{
    uint32_t i;

    lzw->bit_size = lzw->cnt_size + 1;
    lzw->old_code = 0;
    lzw->mask = (1 << lzw->bit_size) - 1;
    lzw->idx_max = lzw->cnt_clear + 1;

    for(i = 0; i < lzw->cnt_clear; i++) {
        lzw->color[i] = i;
        lzw->dict_p[i] = lzw->color + i;
        lzw->dict_length[i] = 1;
    }

    return lzw->idx_max;
}

static struct lzw_info_t * lzw_init(void)
{
    struct lzw_info_t * lzw;
    lzw = gif_malloc(sizeof(struct lzw_info_t));
    if(!lzw) {
        printf("malloc failed for lzw\n");
        goto error;
    }

    lzw->dict_p = (uint8_t **)gif_malloc(LZW_DICT_COUNT_MAX * sizeof(uint8_t *));
    if(!lzw->dict_p) {
        printf("malloc failed for lzw->dict_p\n");
        gif_free(lzw);
        goto error;
    }
    lzw->dict_length = (int *)gif_malloc(LZW_DICT_COUNT_MAX * sizeof(int));
    if(!lzw->dict_p) {
        printf("malloc failed for lzw->dict_p\n");
        gif_free(lzw->dict_p);
        gif_free(lzw);
        goto error;
    }

    lzw_reset(lzw);

    return lzw;
error:
    printf("lzw_init failed\n");
    return NULL;
}

static void lzw_release(struct lzw_info_t * lzw)
{
    gif_free(lzw->dict_p);
    gif_free(lzw->dict_length);
    gif_free(lzw);
}

static int lzw_code(struct lzw_info_t * lzw, int code)
{
    if(code > lzw->idx_max) {
        printf("lzw_code failed: %x > %x\n", code, lzw->idx_max);
        return -1;
    }

    int length;
    if(code < lzw->idx_max) {
        length = lzw->dict_length[code];
        memcpy(lzw->out_ptr, lzw->dict_p[code], length);
        lzw->dict_length[lzw->idx_max] = lzw->dict_length[lzw->old_code] + 1;
        uint8_t * p = lzw->out_ptr - lzw->dict_length[lzw->old_code];
        lzw->dict_p[lzw->idx_max] = p;
        lzw->out_ptr += length;
    } else {
        length = lzw->dict_length[lzw->old_code];
        memcpy(lzw->out_ptr, lzw->dict_p[lzw->old_code], length);
        lzw->out_ptr[length] = *lzw->out_ptr;
        lzw->dict_p[lzw->idx_max] = lzw->out_ptr;
        lzw->dict_length[lzw->idx_max] = length + 1;
        lzw->out_ptr += (length + 1);
    }

    lzw->old_code = code;
    if(lzw->idx_max < LZW_DICT_COUNT_MAX-1) {
        lzw->idx_max ++;
    }
    return lzw->idx_max;
}

static int lzw_data(struct lzw_info_t * lzw, uint8_t * buff_in, uint8_t * buff_out, int count)
{
    uint8_t * sp = buff_in;
    int csize = *buff_in++;

    if(csize > 8) {
        printf("lzw_data failed: size error: %d !!\n", csize);
        return -1;
    }

    lzw->cnt_size = csize;
    lzw->out_ptr = buff_out;
    lzw->cnt_clear = (1 << csize);
    lzw_reset(lzw);

    int i;
    int cnt;
    int code = 0;
    int avail_bits = 0;

    while((cnt = *buff_in++)) {
        for(i = 0; i < cnt; i++) {
            code |= (*buff_in++ << avail_bits);
            avail_bits += 8;
            while(avail_bits >= lzw->bit_size) {
                int sc = lzw->mask & code;
                avail_bits -= lzw->bit_size;
                code >>= lzw->bit_size;

                if(sc == lzw->cnt_clear) {
                    lzw_reset(lzw);
                    /* printf("clear: %p %d\n", outptr, (int)bsize); */
                    continue;
                }

                if(sc == lzw->cnt_clear + 1) {
                    /* printf("lzw end\n"); */
                    break;
                }

                int res = lzw_code(lzw, sc);
                if(res == -1) {
                    printf("lzw_data failed: code error!!\n");
                    return -1;
                }
                if(res > lzw->mask) {
                    if(lzw->bit_size < 12) {
                        lzw->mask = lzw->mask * 2 + 1;
                        lzw->bit_size++;
                    }
                }
            }
        }
    }

    return buff_in - sp;
}

static uint32_t gif_data_read(void * fp, void * buf, uint32_t length, int offset)
{
    if(!fp) {
        printf("gif_data_read failed, fp is NULL\n");
        return 0;
    }

    gif_raw_info_t * gif_fp = (gif_raw_info_t *)fp;
    uint8_t * data_p = (uint8_t *)gif_fp->raw_data;

    if((gif_fp->offset + offset) >= gif_fp->size) {
        printf("gif_data_read failed: error param: %d + %d > %d!!\n", gif_fp->offset, offset, gif_fp->size);
        return 0;
    } else if((gif_fp->offset + offset + length) > gif_fp->size) {
        uint32_t size = gif_fp->size - gif_fp->offset - offset;
        memcpy(buf, data_p + gif_fp->offset + offset, size);
        gif_fp->offset = gif_fp->size;
        return size;
    } else {
        memcpy(buf, data_p + gif_fp->offset + offset, length);
        gif_fp->offset += (offset + length);
        return length;
    }
}


static int gif_data_seek(void * fp, int offset, uint32_t seek_type)
{
    if(!fp) {
        printf("gif_data_seek failed, fp is NULL\n");
        return -1;
    }

    gif_raw_info_t * gif_fp = (gif_raw_info_t *)fp;
    int set = gif_fp->offset;
    switch(seek_type) {
        case SEEK_CUR:
            set += offset;
            break;
        case SEEK_SET:
            set = offset;
            break;
        case SEEK_END:
            set = gif_fp->size - offset;
            break;
        default:
            break;
    }

    if((set > gif_fp->size) || (set < 0)) {
        printf("gif_data_seek failed: error param: offset: %d,  file size: %d!!\n",
               set, gif_fp->size);
        return -1;
    }

    gif_fp->offset = set;
    return 0;
}

/**
   * Check gif file header ：GIF89a
**/
static bool gif_header_check(gif_info_t * gif_info)
{
    char buf[GIF_HEADER_SIZE + 1];
    memset(buf, 0, sizeof(buf));
    if(gif_data_seek(gif_info->file_p, 0, SEEK_SET) == -1) {
        goto error;
    }

    if(gif_data_read(gif_info->file_p, buf, GIF_HEADER_SIZE, 0) != GIF_HEADER_SIZE) {
        printf("read header failed\n");
        goto error;;
    }

    if(strncmp(buf, "GIF89a", GIF_HEADER_SIZE) != 0) {
        printf("It's not a GIF89a file\n");
        goto error;
    }

    return true;
error:
    printf("gif_header_check failed\n");
    return false;
}

/**
   * Reads Logical Screen Descriptor.
**/
static bool gif_lsd_get(gif_info_t * gif_info)
{
    memset(&(gif_info->lsd_info), 0, sizeof(gif_lsd_info_t));
    if(gif_data_read(gif_info->file_p, (char *) & (gif_info->lsd_info), GIF_LSD_SIZE, 0) != GIF_LSD_SIZE)  {
        printf("gif_lsd_get failed : read LSD error\n");
        return false;
    }

    return true;
}

/**
* Reads color table as 256 RGB integer values.
*
* @param ncolors int number of colors to read.
* @return true of false.
*/
static bool gif_color_table_get(gif_info_t * gif_info, lv_color_rgb888_t * buf, uint16_t ncolors)
{
    if(ncolors != 256) {
        printf("gif decoder: the color table is not 256 [%d]!!!\n", ncolors);
    }
    int bytes = 3 * ncolors;

    memset(buf, 0, (3 * GIF_MAX_COLOR_COUNT));
    if(gif_data_read(gif_info->file_p, (char *)buf, bytes, 0) != bytes) {
        printf("gif_color_table_get failed : read color table error\n");
        return false;
    }

    return true;
}

/**
 * Reads 1 frame image.
 */
static bool gif_parse_next_frame(gif_info_t * gif_info)
{
    if(gif_info->frames[gif_info->frames_count] == NULL) {
        gif_info->frames[gif_info->frames_count] = (gif_frame_info_t *)gif_malloc(sizeof(gif_frame_info_t));
        if(gif_info->frames[gif_info->frames_count] == NULL) {
            printf("Error : gif_parse_next_frame: memory overflow 0!! \n");
            return false;
        }
    }

    if(gif_data_read(gif_info->file_p, (char *) & (gif_info->frames[gif_info->frames_count]->image_info), sizeof(gif_image_info_t), 0) != sizeof(gif_image_info_t))  {
        printf("Error : [%s: %d] fail !!\n", __FUNCTION__, __LINE__);
        return false;
    }

    if(gif_info->frames[gif_info->frames_count]->image_info.local_pal_flag) {
        // Read local color table.
        gif_info->frames[gif_info->frames_count]->local_color_table = (lv_color_rgb888_t *)gif_malloc(sizeof(lv_color_rgb888_t) * GIF_MAX_COLOR_COUNT);
        if(gif_info->frames[gif_info->frames_count]->local_color_table == NULL) {
            printf("Error : gif_parse_next_frame: memory overflow 1!! \n");
            return false;
        }
        gif_color_table_get(gif_info, gif_info->frames[gif_info->frames_count]->local_color_table, ((1 << gif_info->frames[gif_info->frames_count]->image_info.pal_bits) * 2));
    } else {
        // No local color table.
        gif_info->frames[gif_info->frames_count]->local_color_table = NULL;
    }

    // Save this as the decoding position pointer.
#if(GIF_USE_FILE_SYSTEM == 1)
    uint32_t offset = 0;
    if(lv_fs_tell((lv_fs_file_t *)gif_info->file_p, &offset) == LV_FS_RES_OK) {
        gif_info->frames[gif_info->frames_count]->file_offset = offset;
    } else {
        gif_info->frames[gif_info->frames_count]->file_offset = 0;
        printf("Gif error: can't get the file offset!!!\n");
    }
#else
    gif_raw_info_t * gif_fp = (gif_raw_info_t *)gif_info->file_p;
    gif_info->frames[gif_info->frames_count]->file_offset = gif_fp->offset;
#endif

    // False decode pixel data to advance buffer.
    char buf[256];
    if(gif_data_read(gif_info->file_p, buf, 1, 0) == 0) {  //bits of LZW initial coding table
        printf("Error : [%s: %d] fail !!\n", __FUNCTION__, __LINE__);
        return false;
    }

    uint8_t block_size;
    do {
        if(gif_data_read(gif_info->file_p, &block_size, 1, 0) == 0) {
            printf("Error : [%s: %d] fail !!\n", __FUNCTION__, __LINE__);
            return false;
        }

        if(block_size > 0) {
            if(gif_data_read(gif_info->file_p, buf, block_size, 0) == 0) {
                printf("Error : [%s: %d] fail !!\n", __FUNCTION__, __LINE__);
                return false;
            }
        }
    } while(block_size > 0);
#if(GIF_USE_FILE_SYSTEM == 1)
    if(lv_fs_tell((lv_fs_file_t *)gif_info->file_p, &offset) == LV_FS_RES_OK) {
        gif_info->frames[gif_info->frames_count]->frame_size = offset - gif_info->frames[gif_info->frames_count]->file_offset;
    } else {
        gif_info->frames[gif_info->frames_count]->frame_size = 0;
        printf("Gif error: can't get the file offset 1!!!\n");
    }
#else
    gif_info->frames[gif_info->frames_count]->frame_size = gif_fp->offset - gif_info->frames[gif_info->frames_count]->file_offset;
#endif
    gif_info->frames_count++;

    return true;
}

/**
 * Parse extention graphic control.
 */
static bool gif_parse_graphic_control(gif_info_t * gif_info, uint8_t size)
{
    uint32_t i;

    if(gif_info->frames[gif_info->frames_count] == NULL) {
        gif_info->frames[gif_info->frames_count] = (gif_frame_info_t *)gif_malloc(sizeof(gif_frame_info_t));
        if(gif_info->frames[gif_info->frames_count] == NULL) {
            printf("Error : gif_parse_graphic_control: memory overflow!! \n");
            return false;
        }
    }

    if(gif_data_read(gif_info->file_p, (char *) & (gif_info->frames[gif_info->frames_count]->graphic_ctrl), size, 0) != size) {
        printf("Error : [%s: %d] fail !!\n", __FUNCTION__, __LINE__);
        return false;
    }

    for(i = 0; i < gif_info->frames_count; i++){
        if(gif_info->frames[i]->graphic_ctrl.disposal_method == 2) gif_info->hasdm2 = true;
    }

    return true;
}

static bool gif_get_one_frame(gif_info_t * gif_info, void * buf, uint32_t frameIndex)
{
    if(frameIndex >= gif_info->frames_count) {
        printf("gif_get_one_frame: error frame index : %d >= %d \n", frameIndex, gif_info->frames_count);
        return false;
    }

    uint8_t * frame_buf = (uint8_t *)gif_malloc(gif_info->frames[frameIndex]->frame_size);
    if(frame_buf == NULL) {
        printf("gif_get_one_frame: memory overflow!! \n");
        return false;
    }

    bool res = false;
    if(gif_data_seek(gif_info->file_p, gif_info->frames[frameIndex]->file_offset, SEEK_SET) == 0) {
        uint32_t size = gif_data_read(gif_info->file_p, frame_buf, gif_info->frames[frameIndex]->frame_size, 0);
        if(size == gif_info->frames[frameIndex]->frame_size) {
            if(lzw_data(gif_info->lzw_info, frame_buf, buf, gif_info->frames[frameIndex]->frame_size) != -1) {
                res = true;
            }
        }
    }

    gif_free(frame_buf);
    return res;
}

static void gif_render_current_frame(gif_info_t * info)
{
    int i = 0, j = 0;
    gif_frame_info_t * frame_info = info->frames[info->current_frame_index];

    uint32_t frame_buf_size = (frame_info->image_info.width) * (frame_info->image_info.height);
    uint8_t * frame_buf = (uint8_t *)gif_malloc(frame_buf_size);
    if(frame_buf == NULL) {
        LOGE("gif_render_current_frame: memory overflow!! \n");
        return;
    }

    gif_get_one_frame(info, frame_buf, info->current_frame_index);

    lv_color_rgb888_t * color_table = info->global_color_table;
    if(frame_info->image_info.local_pal_flag) {
        color_table = frame_info->local_color_table;
    }

    if((info->lsd_info.global_pal_flag == 0) && (frame_info->image_info.local_pal_flag == 0)) {
        LOGE("gif error : no color table!!!!!\n");
    }

    switch(info->render_color_format) {
        case COLOR_FORMAT_RGB565:
            if(info->render_bmp == NULL) {
                info->render_bmp = (uint8_t *)gif_malloc(info->lsd_info.width * info->lsd_info.height * 3);
                if(info->render_bmp == NULL) {
                    LOGE("gif_render_current_frame: memory overflow 1!! \n");
                    break;
                }
            }

            //for frame with disposal_method 2, keep alpha value
            if(info->render_bmp_a == NULL && info->hasdm2) {
                info->render_bmp_a = (uint8_t *)gif_malloc(info->lsd_info.width * info->lsd_info.height * 3);
                if(info->render_bmp_a == NULL) {
                    gif_free(info->render_bmp);
                    LOGE("gif_render_current_frame: memory overflow 2!! \n");
                    break;
                }
                if(info->current_frame_index == 0)
                    memset(info->render_bmp_a, 0x00, info->lsd_info.width * info->lsd_info.height);
            }
            if(info->render_bmp_a) {
                for(i = 0; i < info->lsd_info.width * info->lsd_info.height; i++) {
                    info->render_bmp[i * 3 + 2] = info->render_bmp_a[i];
                }
            }

            for(i = 0; i < frame_info->image_info.height; i++) {
                for(j = 0; j < frame_info->image_info.width; j++) {
                    unsigned color_index = frame_buf[i * frame_info->image_info.width + j];
                    if(!frame_info->graphic_ctrl.trans_enable || color_index != frame_info->graphic_ctrl.trans_color_index) {
                        lv_color_rgb888_t * color = color_table + color_index;
                        lv_color16_t color565 = LV_COLOR_MAKE(color->r, color->g, color->b);
                        uint32_t position = (i + frame_info->image_info.top) * info->lsd_info.width + (j + frame_info->image_info.left);
                       info->render_bmp[position * 3] = color565.full & 0xFF;
                       info->render_bmp[position * 3 + 1] = color565.full >> 8;
                       info->render_bmp[position * 3 + 2] = 0xff;
                       if(info->render_bmp_a) {
                           if(frame_info->graphic_ctrl.disposal_method <= 1) {
                               info->render_bmp_a[position] = 0xff;
                           } else if(frame_info->graphic_ctrl.disposal_method == 2) {
                               info->render_bmp_a[position] = 0;
                           }
                       }
                    }
                }
            }
            break;

        default:
            printf("gif_render_current_frame: not support this color format(%d) yet!!\n", info->render_color_format);
            break;
    }

    gif_free(frame_buf);
}

/*Need gif_free(buf) after buffer is no used;*/
static uint8_t * gif_read_file(const char * path, uint32_t * size)
{
    lv_fs_file_t file;
    uint32_t file_size = *size;

    lv_fs_res_t res = lv_fs_open(&file, path, LV_FS_MODE_RD);
    if(res == LV_FS_RES_OK) {
        if(file_size == 0) {
            if(LV_FS_RES_OK != lv_fs_size(&file, &file_size)) {
                printf("[%s]: lv_fs_size failed\n", __func__);
                goto err;
            }
        }

        if(file_size <= 0) {
            printf("[%s]: lv_fs_size get size=%d wrong\n", __func__, file_size);
            goto err;
        }

        uint32_t br = 0;
        uint8_t * buf = gif_malloc(file_size);
        if(buf == NULL) {
            printf("[%s] No memory !!!\n", __func__);
            goto err;
        }
        memset(buf, 0, file_size);
        res = lv_fs_read(&file, buf, file_size, &br);
        *size = file_size;
        lv_fs_close(&file);
        return buf;
    } else {
        printf("[%s] error,path = %s,errno = %d !!!\n", __func__, path, res);
        return NULL;
    }

err:
    lv_fs_close(&file);
    return NULL;
}

static gif_info_t * _gif_open(const void * src)
{
    gif_info_t * gif_info = gif_malloc(sizeof(gif_info_t));
    if(!gif_info) {
        printf("malloc gif_info failed\n");
        goto error;
    }
    memset(gif_info, 0x0, sizeof(gif_info_t));
    gif_info->lzw_info = lzw_init();
    if(!gif_info->lzw_info) {
        goto error_gif_info;
    }

    gif_raw_info_t * gif_fp = (gif_raw_info_t *)gif_malloc(sizeof(gif_raw_info_t));
    if(gif_fp == NULL) {
        printf("malloc gif_fp failed\n");
        goto error_lzw;
    }

    lv_img_src_t src_type = lv_img_src_get_type(src);          /*Get the source type*/
    if(src_type == LV_IMG_SRC_FILE) {
        uint8_t * data_buf = NULL;
        uint32_t data_size = 0;
        data_buf = gif_read_file(src, &data_size);
        if(data_buf == NULL) {
            goto error_lzw;
        }

        gif_fp->raw_data = data_buf;
        gif_fp->offset = 0;
        gif_fp->size = data_size;
    }
    else if(src_type == LV_IMG_SRC_VARIABLE) {
        lv_img_dsc_t * img = (lv_img_dsc_t *)src;
        gif_fp->raw_data = (void *)img->data;
        gif_fp->offset = 0;
        gif_fp->size = img->data_size;
    }

    gif_fp->src_type = src_type;
    gif_info->file_p = gif_fp;

    gif_info->not_free_flag = GIF_NOT_FREE_FLAG;
    return gif_info;

error_lzw:
    lzw_release(gif_info->lzw_info);
error_gif_info:
    gif_free(gif_info);

error:
    printf("_gif_open failed\n");
    return NULL;
}

static void _gif_close(gif_info_t * info)
{
    if(info == NULL) {
        return;
    }

    if(info->not_free_flag != GIF_NOT_FREE_FLAG) {
        return;
    }

    gif_raw_info_t * gif_fp = (gif_raw_info_t *)info->file_p;
    if(gif_fp->src_type == LV_IMG_SRC_FILE) {
        gif_free(gif_fp->raw_data);
    }

    gif_free(info->file_p);
    gif_free(info->render_bmp);
    if(info->render_bmp_a) gif_free(info->render_bmp_a);

    for(int i = 0; i < info->frames_count; i++) {
        gif_free(info->frames[i]->local_color_table);
        gif_free(info->frames[i]);
    }

    lzw_release(info->lzw_info);
    info->not_free_flag = 0;
    gif_free(info);
}

static bool gif_file_parse(gif_info_t * gif_info)
{
    if(!gif_header_check(gif_info))
        return false;

    if(!gif_lsd_get(gif_info))
        return false;

    if(gif_info->lsd_info.global_pal_flag) {
        if(!gif_color_table_get(gif_info, gif_info->global_color_table, (1 << gif_info->lsd_info.pal_bits) * 2))
            return false;
    }

    // parser frames one by one
    do {
        uint8_t flag;
        if(gif_data_read(gif_info->file_p, &flag, 1, 0) == 0)
            return true;

        switch(flag) {
            case 0x2C:
                if(gif_parse_next_frame(gif_info) == false) {
                    return false;
                }
                break;
            case 0x21: {
                uint8_t block_size = 0;
                if(gif_data_read(gif_info->file_p, &flag, 1, 0) == 0)
                    return false;

                if(gif_data_read(gif_info->file_p, &block_size, 1, 0) == 0)
                    return false;

                if(flag == 0xf9) {
                    if(gif_parse_graphic_control(gif_info, block_size) == false)
                        return false;
                } else if(flag == 0xfe || flag == 0x01 || flag == 0xff) {
                    do {
                        if(gif_data_read(gif_info->file_p, &block_size, 1, block_size) == 0) {
                            return false;
                        }
                    } while(block_size > 0);
                } else {
                    LOGW("gif_file_parse: unkown Extension: %x\n", flag);
                    return false;
                }
                break;
            }
            case 0x3b:
                LOGD("gif_file_parse : End Stream!\n");
                return true;
            default:
                LOGD("gif_file_parse: unkown: %x\n", flag);
                break;
        }
    } while(1);

    return true;
}

/* return in ms */
static int get_frame_delay(gif_info_t * info, int frame_idx)
{
    return info->frames[frame_idx]->graphic_ctrl.delay_time * 10;
}

static void gif_anim_task(lv_task_t * task)
{
    gif_info_t * info = (gif_info_t *)task->user_data;
    int delay_ms;

    info->current_frame_index++;
    if(info->current_frame_index == info->frames_count) {  /* last frame */
        info->once_done = 1;
        if(info->once) {
            lv_task_del(info->gif_anim_task);
            info->gif_anim_task = NULL;
            return;
        } else {
            info->current_frame_index = 0;
        }
    }

    gif_render_current_frame(info);
    info->gif_frame.data = info->render_bmp;
    lv_img_cache_invalidate_src(&info->gif_frame);
    lv_img_set_src(info->img1, &info->gif_frame);
    lv_obj_set_pos(info->img1, info->x, info->y);

    delay_ms = get_frame_delay(info, (info->current_frame_index+1)==info->frames_count?0:(info->current_frame_index+1));

    lv_task_set_period(info->gif_anim_task, delay_ms);
}

typedef struct gif_ext_attr_s {
    gif_info_t *info_p;
} gif_ext_attr_t;

static lv_signal_cb_t ancestor_signal;

static lv_res_t _gif_signal(lv_obj_t * cont, lv_signal_t sign, void * param)
{
    lv_res_t res;

    /* Include the ancient signal function */
    res = ancestor_signal(cont, sign, param);
    if(res != LV_RES_OK) return res;

    gif_ext_attr_t *ext_attr = (gif_ext_attr_t *)lv_obj_get_ext_attr(cont);
    if(ext_attr == NULL) return res;

    lv_obj_t *gif_par = ext_attr->info_p->par;
    if(gif_par) {
        switch(sign) {
            case LV_SIGNAL_PRESSING:
            case LV_SIGNAL_PRESSED:
            case LV_SIGNAL_RELEASED:
            case LV_SIGNAL_PRESS_LOST:
            case LV_SIGNAL_LONG_PRESS:
            case LV_SIGNAL_LONG_PRESS_REP:
                gif_par->signal_cb(gif_par, sign, NULL);
                break;
            default:
              break;
        }
    }

    if(sign == LV_SIGNAL_CLEANUP) {
        if(ext_attr->info_p->gif_anim_task) {
            lv_task_del(ext_attr->info_p->gif_anim_task);
            ext_attr->info_p->gif_anim_task = NULL;
        }
        _gif_close(ext_attr->info_p);
        ext_attr->info_p = NULL;
    }

    return res;
}

void * gif_open(lv_obj_t * par, const void * src, int once)
{
    lv_obj_t * parent;
    gif_info_t * info;

    parent = par ? par : lv_disp_get_scr_act(NULL);

    info = _gif_open(src);
    if(!info) {
        goto error;
    }

    if(!gif_file_parse(info)) {
        printf("gif_file_parse failed\n");
        goto error_close;
    }
    info->render_color_format = COLOR_FORMAT_RGB565;
    info->current_frame_index = 0;

    info->gif_frame.header.w = info->lsd_info.width;
    info->gif_frame.header.h = info->lsd_info.height;
    info->gif_frame.header.cf = LV_IMG_CF_TRUE_COLOR_ALPHA;
    info->gif_frame.data_size = info->lsd_info.width * info->lsd_info.height * 3;  /* RGB565 + alpha */

    info->once = once;
    info->par = par;

    info->container = lv_obj_create(parent, NULL);
    if(!info->container) {
        printf("gif open failed 0\n");
        goto error_close;
    }

    lv_obj_set_size(info->container, info->lsd_info.width, info->lsd_info.height);

    info->img1 = lv_img_create(info->container, NULL);
    if(!info->img1) {
        printf("gif open failed 1\n");
        lv_obj_del(info->container);
        goto error_close;
    }

    gif_render_current_frame(info);
    info->gif_frame.data = info->render_bmp;
    lv_img_cache_invalidate_src(&info->gif_frame);
    lv_img_set_src(info->img1, &info->gif_frame);
    lv_obj_set_pos(info->img1, info->x, info->y);

    int delay_ms = get_frame_delay(info, (info->current_frame_index+1)==info->frames_count?0:(info->current_frame_index+1));

    info->gif_anim_task = lv_task_create(gif_anim_task, delay_ms, LV_TASK_PRIO_MID, (void *)info);
    if(!info->gif_anim_task) {
        printf("gif open: lv_task_create failed\n");
        lv_obj_del(info->container);
        goto error_close;
    }

    gif_ext_attr_t *ext_attr = lv_obj_allocate_ext_attr(info->container, sizeof(gif_ext_attr_t));
    ext_attr->info_p = info;

    if(ancestor_signal == NULL) ancestor_signal = lv_obj_get_signal_cb(info->container);
    lv_obj_set_signal_cb(info->container, _gif_signal);

    return info;
error_close:
    _gif_close(info);
error:
    printf("play_gif failed\n");
    return NULL;
}

int gif_close(gif_info_t * info)
{
    if(info == NULL) {
        return -1;
    }

    if(info->not_free_flag != GIF_NOT_FREE_FLAG) {
        return 0;
    }

    if(info->gif_anim_task) {
        lv_task_del(info->gif_anim_task);
        info->gif_anim_task = NULL;
    }

    lv_obj_del(info->container);
    _gif_close(info);
    return 0;
}

#endif /* USE_LV_DRAW_GIF_PLAYER */
